package org.xl.main.jenkins;

import cn.hutool.core.io.IORuntimeException;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;

import java.io.IOException;
import java.net.HttpURLConnection;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;

/**
 * Jenkins Job触发工具类
 */
public class JenkinsJobTrigger {

    private final String jenkinsUrl;
    private final String username;
    private final String apiToken;
    private final String projectSuffix;
    private final int timeout; // 超时时间(秒)

    /**
     * 构造函数
     *
     * @param jenkinsUrl Jenkins服务器地址，如 http://jenkins.example.com
     * @param username   Jenkins用户名
     * @param apiToken   Jenkins API Token
     * @param timeout    请求超时时间(秒)
     */
    public JenkinsJobTrigger(String jenkinsUrl, String projectSuffix, String username, String apiToken, int timeout) {
        this.jenkinsUrl = jenkinsUrl.endsWith("/") ? jenkinsUrl.substring(0, jenkinsUrl.length() - 1) : jenkinsUrl;
        this.username = username;
        this.apiToken = apiToken;
        this.timeout = timeout;
        this.projectSuffix = projectSuffix;
    }

    /**
     * 触发简单的Job构建（无参数）
     *
     * @param jobName 任务名称
     * @return 构建队列ID
     * @throws IOException 当触发失败时抛出
     */
    public int triggerJob(String jobName) throws IOException {
        return triggerJob(jobName, null, null);
    }

    /**
     * 触发带参数的Job构建
     *
     * @param jobName      任务名称
     * @param parameters   构建参数
     * @param buildCause  构建原因描述
     * @return 构建队列ID
     * @throws IOException 当触发失败时抛出
     */
    public int triggerJob(String jobName, Map<String, Object> parameters, String buildCause) throws IOException {
        String triggerUrl = String.format("%s%s%s/build", jenkinsUrl,projectSuffix, jobName);

        HttpRequest request = HttpRequest.post(triggerUrl)
                .header("Authorization", getAuthHeader())
                .timeout(timeout * 1000);

        // 添加构建参数
        if (parameters != null && !parameters.isEmpty()) {
            triggerUrl = String.format("%s%s%s/buildWithParameters", jenkinsUrl,projectSuffix, jobName);
            request = HttpRequest.post(triggerUrl)
                    .header("Authorization", getAuthHeader())
                    .timeout(timeout * 1000)
                    .form(parameters);
        }

        // 添加构建原因
        if (StrUtil.isNotBlank(buildCause)) {
            request.form("cause", buildCause);
        }

        try {
            HttpResponse response = request.execute();
            return handleTriggerResponse(response, jobName);
        } catch (IORuntimeException e) {
            throw new IOException("Failed to trigger job: " + jobName, e);
        }
    }

    /**
     * 使用Crumb Issuer触发Job（用于CSRF保护开启的Jenkins）
     *
     * @param jobName      任务名称
     * @param parameters   构建参数
     * @param buildCause  构建原因
     * @return 构建队列ID
     * @throws IOException 当触发失败时抛出
     */
    public int triggerJobWithCrumb(String jobName, Map<String, Object> parameters, String buildCause) throws IOException {
        // 首先获取Crumb
        JSONObject crumb = getCrumb();
        String crumbField = crumb.getStr("crumbRequestField");
        String crumbValue = crumb.getStr("crumb");

        String triggerUrl = String.format("%s%s%s/build", jenkinsUrl,projectSuffix, jobName);

        HttpRequest request = HttpRequest.post(triggerUrl)
                .header("Authorization", getAuthHeader())
                .header(crumbField, crumbValue)
                .timeout(timeout * 1000);

        // 添加构建参数
        if (parameters != null && !parameters.isEmpty()) {
            triggerUrl = String.format("%s%s%s/buildWithParameters", jenkinsUrl, projectSuffix, jobName);
            request = HttpRequest.post(triggerUrl)
                    .header("Authorization", getAuthHeader())
                    .header(crumbField, crumbValue)
                    .timeout(timeout * 1000)
                    .form(parameters);
        }

        // 添加构建原因
        if (StrUtil.isNotBlank(buildCause)) {
            request.form("cause", buildCause);
        }

        try {
            HttpResponse response = request.execute();
            return handleTriggerResponse(response, jobName);
        } catch (IORuntimeException e) {
            throw new IOException("Failed to trigger job with crumb: " + jobName, e);
        }
    }

    /**
     * 获取Jenkins Crumb（用于CSRF保护）
     *
     * @return Crumb信息
     * @throws IOException 当获取失败时抛出
     */
    private JSONObject getCrumb() throws IOException {
        String crumbUrl = jenkinsUrl + "/crumbIssuer/api/json";
        try {
            HttpResponse response = HttpRequest.get(crumbUrl)
                    .header("Authorization", getAuthHeader())
                    .timeout(timeout * 1000)
                    .execute();

            if (response.getStatus() != 200) {
                throw new IOException("Failed to get crumb. HTTP status: " + response.getStatus());
            }

            return JSONUtil.parseObj(response.body());
        } catch (IORuntimeException e) {
            throw new IOException("Failed to get crumb", e);
        }
    }

    /**
     * 处理触发响应
     */
    private int handleTriggerResponse(HttpResponse response, String jobName) throws IOException {
        int status = response.getStatus();

        if (status == HttpURLConnection.HTTP_CREATED) {
            // 成功触发，从Location头中提取队列ID
            String location = response.header("Location");
            if (location != null && location.contains("queue/item/")) {
                String[] parts = location.split("/");
                String queueIdStr = parts[parts.length - 1];
                return Integer.parseInt(queueIdStr);
            }
            throw new IOException("Trigger successful but failed to parse queue ID from location: " + location);
        } else if (status == HttpURLConnection.HTTP_NOT_FOUND) {
            throw new IOException("Job not found: " + jobName);
        } else if (status == HttpURLConnection.HTTP_FORBIDDEN) {
            throw new IOException("Permission denied for job: " + jobName);
        } else {
            throw new IOException("Failed to trigger job. HTTP status: " + status + ", Response: " + response.body());
        }
    }

    /**
     * 等待构建完成
     *
     * @param queueId     队列ID
     * @param maxWaitTime 最大等待时间(秒)
     * @return 构建号，如果超时返回-1
     * @throws IOException 当等待过程中发生错误时抛出
     */
    public String waitForBuildCompletion(int queueId, int maxWaitTime) throws IOException {
        String queueUrl = String.format("%s%squeue/item/%d/api/json", jenkinsUrl,projectSuffix, queueId);
        long startTime = System.currentTimeMillis();

        while (System.currentTimeMillis() - startTime < maxWaitTime * 1000L) {
            try {
                HttpResponse response = HttpRequest.get(queueUrl)
                        .header("Authorization", getAuthHeader())
                        .timeout(timeout * 1000)
                        .execute();

                if (response.getStatus() == 200) {
                    JSONObject queueInfo = JSONUtil.parseObj(response.body());
                    if (!queueInfo.getBool("blocked", true) && queueInfo.getBool("cancelled", false)) {
                        throw new IOException("Build was cancelled in queue");
                    }

                    // 检查是否已经离开队列（开始构建）
                    if (queueInfo.get("executable") != null) {
                        JSONObject executable = queueInfo.getJSONObject("executable");
                        return executable.getStr("number");
                    }
                } else if (response.getStatus() == 404) {
                    // 队列项可能已经离开队列
                    return "-1";
                }

                TimeUnit.SECONDS.sleep(2); // 等待2秒后再次检查
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                throw new IOException("Wait interrupted", e);
            } catch (IORuntimeException e) {
                throw new IOException("Failed to check queue status", e);
            }
        }

        throw new IOException("Wait for build completion timeout after " + maxWaitTime + " seconds");
    }

    /**
     * 获取构建状态
     *
     * @param jobName     任务名称
     * @param buildNumber 构建号
     * @return 构建状态
     * @throws IOException 当获取状态失败时抛出
     */
    public String getBuildStatus(String jobName, String buildNumber) throws IOException {
        String apiUrl = String.format("%s%s%s%s/api/json", jenkinsUrl,projectSuffix, jobName, buildNumber);
        try {
            HttpResponse response = HttpRequest.get(apiUrl)
                    .header("Authorization", getAuthHeader())
                    .timeout(timeout * 1000)
                    .execute();

            if (response.getStatus() != 200) {
                throw new IOException("Failed to get build status. HTTP status: " + response.getStatus());
            }

            JSONObject json = JSONUtil.parseObj(response.body());
            return json.getBool("building", false) ? "BUILDING" : json.getStr("result", "UNKNOWN");
        } catch (IORuntimeException e) {
            throw new IOException("Failed to get build status", e);
        }
    }

    /**
     * 获取基本认证头
     */
    private String getAuthHeader() {
        String auth = username + ":" + apiToken;
        return "Basic " + Base64.getEncoder().encodeToString(auth.getBytes());
    }

    // ==================== 日志获取相关方法 ====================

    /**
     * 获取构建的实时日志（流式输出）
     *
     * @param jobName      任务名称
     * @param buildNumber  构建号
     * @param logConsumer  日志消费者
     * @param pollInterval 轮询间隔(毫秒)
     */
    public void streamConsoleLog(String jobName, String buildNumber, Consumer<String> logConsumer, long pollInterval)
            throws IOException {
        String logUrl = buildLogUrl(jobName, buildNumber);
        long offset = 0;
        boolean isBuilding = true;
        String lastLog = "";

        while (isBuilding) {
            try {
                HttpResponse response = HttpRequest.get(logUrl)
                        .header("Authorization", getAuthHeader())
                        .form("start", offset)
                        .execute();

                if (response.getStatus() != 200) {
                    throw new IOException("Failed to fetch logs. HTTP status: " + response.getStatus());
                }
                String newLog = response.body();
                if (StrUtil.isNotBlank(newLog)) {
                    logConsumer.accept(newLog);
                    offset += newLog.length();
                }

                // 检查是否还有更多数据
                String moreData = response.header("X-More-Data");
                isBuilding = "true".equalsIgnoreCase(moreData);

                if (isBuilding) {
                    TimeUnit.MILLISECONDS.sleep(pollInterval);
                }
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                throw new IOException("Log streaming interrupted", e);
            } catch (IORuntimeException e) {
                throw new IOException("Failed to stream logs", e);
            }
        }
    }

    /**
     * 获取构建的完整日志（一次性获取）
     *
     * @param jobName     任务名称
     * @param buildNumber 构建号
     * @return 完整的控制台日志
     */
    public String getFullConsoleLog(String jobName, String buildNumber) throws IOException {
        String logUrl = buildLogUrl(jobName, buildNumber);

        try {
            HttpResponse response = HttpRequest.get(logUrl)
                    .header("Authorization", getAuthHeader())
                    .execute();

            if (response.getStatus() != 200) {
                throw new IOException("Failed to fetch full logs. HTTP status: " + response.getStatus());
            }

            return response.body();
        } catch (IORuntimeException e) {
            throw new IOException("Failed to get full console log", e);
        }
    }

    /**
     * 获取构建的实时日志并等待完成
     *
     * @param jobName      任务名称
     * @param buildNumber  构建号
     * @param logConsumer  日志消费者
     * @param pollInterval 轮询间隔(毫秒)
     * @param maxWaitTime  最大等待时间(秒)
     * @return 构建最终状态
     */
    public String streamLogAndWait(String jobName, String buildNumber, Consumer<String> logConsumer,
                                   long pollInterval, int maxWaitTime) throws IOException {
        long startTime = System.currentTimeMillis();
        String logUrl = buildLogUrl(jobName, buildNumber);
        long offset = 0;
        boolean isBuilding = true;

        while (isBuilding && (System.currentTimeMillis() - startTime < maxWaitTime * 1000L)) {
            try {
                HttpResponse response = HttpRequest.get(logUrl)
                        .header("Authorization", getAuthHeader())
                        .form("start", offset)
                        .execute();

                if (response.getStatus() != 200) {
                    throw new IOException("Failed to fetch logs. HTTP status: " + response.getStatus());
                }

                String newLog = response.body();
                if (StrUtil.isNotBlank(newLog)) {
                    logConsumer.accept(newLog);
                    offset += newLog.length();
                }

                // 检查构建状态
                String moreData = response.header("X-More-Data");
                isBuilding = "true".equalsIgnoreCase(moreData);

                if (isBuilding) {
                    TimeUnit.MILLISECONDS.sleep(pollInterval);
                }
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                throw new IOException("Log streaming interrupted", e);
            } catch (IORuntimeException e) {
                throw new IOException("Failed to stream logs", e);
            }
        }

        if (isBuilding) {
            throw new IOException("Wait for build completion timeout after " + maxWaitTime + " seconds");
        }

        return getBuildStatus(jobName, buildNumber);
    }

    /**
     * 构建日志URL
     */
    private String buildLogUrl(String jobName, String buildNumber) {
        String encodedJobName = encodeJobName(jobName);
        return String.format("%s%s%s/%s/logText/progressiveText", jenkinsUrl,projectSuffix, encodedJobName, buildNumber);
    }


    /**
     * 编码Job名称（处理特殊字符）
     */
    private String encodeJobName(String jobName) {
        // 简单的URL编码，实际可能需要更复杂的处理
        return jobName.replace(" ", "%20")
                .replace("/", "%2F");
    }




    /**
     * 简单示例用法
     */
    public static void main(String[] args) {
        // 配置Jenkins连接信息
        String jenkinsUrl = "http://172.16.76.13:8080";
        String username = "huanglong1";
        String apiToken = "111bdc3b4b075e18d9949d028df1967f46";
        String jobName = "so-integration-run-risk-tz";
        String buildNumber = "lastBuild"; // 构建号
        String projectSuffix="/job/JY-OO-01-SJ-22-016-TQ/job/";

        // 创建Job触发器
        JenkinsJobTrigger trigger = new JenkinsJobTrigger(jenkinsUrl,projectSuffix, username, apiToken, 30);

        try {
//            // 示例1: 触发无参数Job
//            System.out.println("触发无参数Job...");
//            int queueId1 = trigger.triggerJob(jobName);
//            System.out.println("触发成功，队列ID: " + queueId1);
//
//            // 等待构建完成
//            String buildNumber = trigger.waitForBuildCompletion(queueId1, 300);
//            if (ObjectUtil.notEqual(buildNumber,"-1")) {
//                String status = trigger.getBuildStatus(jobName, buildNumber);
//                System.out.println("构建完成，构建号: " + buildNumber + ", 状态: " + status);
//            }

            // 示例2: 触发带参数Job
            Map<String, Object> parameters = new HashMap<>();
            parameters.put("gitlabBranch", "dev");

            int queueId2 = trigger.triggerJobWithCrumb(jobName, parameters, "Automated build triggered by API");
            System.out.println("触发成功，队列ID: " + queueId2);

            trigger.streamConsoleLog(jobName, buildNumber, log -> {
                System.out.print(log);
            }, 1000);

        } catch (IOException e) {
            System.err.println("触发Job失败: " + e.getMessage());
            e.printStackTrace();
        }
    }
}